package paws.core

import scala.actors._
import Actor._
import util.SimulationUtils._
import scala.reflect.BeanProperty


object NodeType extends Enumeration {
  val Source,
      Intermediary,
      Destination = Value
}


object PowerLevel extends Enumeration {
  val Sleep,
      Low,
      Medium,
      High = Value
}

object NodeStatus extends Enumeration {
  val New,
      Active,
      Sleeping = Value
}


class NodeParameters {
  import PowerLevel._
  import PowerLevel.{Value => PowerLevel}
  
  @BeanProperty 
  var range = 
    Map(Sleep  ->  0.0, Low    -> 10.0, 
        Medium -> 13.0, High   -> 100.0)
  
  @BeanProperty
  var powerLevel =
    Map(Sleep  ->  500.0, Low    ->  750.0,
        Medium ->  800.0, High   -> 1000.0)
                  
  @BeanProperty
  var messagePower = 0.02
  
  @BeanProperty 
  var serviceRate = 0
  
  @BeanProperty 
  var serviceVariance = 0
  
  @BeanProperty 
  var sleepThreshold = 0
  
  def copy() = {
    val np = new NodeParameters
    np.range = range
    np.serviceRate = serviceRate
    np.serviceVariance = serviceVariance
    np.sleepThreshold = sleepThreshold
    np
  }
}


class Node(sim: Simulation, p: NodeParameters) extends Actor {    
  // For some reason, the type alias below doesn't work for constructors.
  // Strangeness.
  
  import NodeType._
  import PowerLevel._
  import NodeStatus._
  type NodeType = NodeType.Value
  
  private var id = 0
  private var simulation = sim
  private var params = p
  private var location = (0.0, 0.0)
  private var nodeType = Intermediary
  private var power = 1000.0
  private var level = High
  private var running = false
  private var status = New
  private val me: Node = this
  private var log: Logger = null
  private var elog: Logger = null 
  private var time: Long = -1
  private val router = sim.getRouter
  
  
  def this(i: Int, s: Simulation, p: NodeParameters) = { this(s, p); id = i }  
  def this(i: Int, t: NodeType.Value, s: Simulation, p: NodeParameters) =
    { this(i, s, p); nodeType = t }  
  def this(i: Int, t: NodeType.Value, s: Simulation, p: NodeParameters, x: Double, y: Double) =
    { this(i, t, s, p); location = (x, y) }     
  
  def getID() = id
  def setID(nid: Int) = (id = nid)
  
  // def getSimulation() = simulation
  // def setSimulation(sim: Simulation) = (simulation = sim)
  
  def getParameters() = params.copy()
  def setParameters(np: NodeParameters) = { params = np }
    
  def getLocation() = location.copy()
  def setLocation(x: Double, y: Double) = (location = (x, y))
    
  def getNodeType() = nodeType
  def setNodeType(ntype: NodeType) = (nodeType = ntype)
    
  def getPower() = power
  def setPower(pwr: Double) = (power = pwr)
  
  def getStatus = status
  
  def isRunning = running
  
  def act {
    // me = self.asInstanceOf[Node]
    // log ! "" + me + " is waiting for ticks."
    loop{
      if(!running) exit
      react {
        case Tick(t) => 
          time = t
          // log ! "" + me + " got tick " + t
          // Only participate in the current tick if device is still active
          if(status == Active)
            // + calculate reliability here +
            doTickActions()
          else
            sim ! Tock(time)
        case ExitNode  => 
          log ! "" + me + " exiting."
          running = false
          sim ! NodeExited
      }
    }
  }
  
  private def doTickActions(): Unit = {    
    generateMsgs()    
    var done = false    
    loop {
      if(done) {
        // log ! "" + me + " sent tock " + time
        if(power < params.sleepThreshold) {   // if power gets too low, 
          me.status = Sleeping                //   put node to sleep
          router.removeNode(me)               //   and remove from network graph
          log ! me + " is going to sleep."
        }
        sim ! Tock(time)
        act
      }
      
      react {
        case FinishedMsgs =>
          // log ! "" + me + " finished events for tick " + time
          done = true
        case FakeMessage =>
          // do a whole lotta nothing
        case msg: Message =>
          msg.dst match {
            case `me`  =>  processMsg(msg)
            case other =>
              router.getNextHop(me, other) match {
                case None    => 
                  log ! "" + me + ": " + msg + " has no route"
                case Some(n) => 
                  // log ! "" + me + ": forwarded " + msg + " to " + n
                  
                  // update power
                  me.power  -= params.messagePower      // deduct power for msg transmission
                  msg.power += params.messagePower      // record it in the msg itself
                  
                  // update reliability
                  val radius = params.range(High)
                  val area = Math.Pi * radius * radius
                  val devices = router.getNeighbors(me)
                  val density = devices/area
                  msg.reliability += density
                  
                  // delay will be calculated when msg 
                  // received by destination node
                  n ! msg                               // send message on
              }
          }
        case other => 
          elog ! "Received unknown message: " + other
      }
    }
  }
  
  def processMsg(msg: Message) {
    log ! "" + me + " <- " + msg + " at tick " + time
    val power = msg.power
    val reliability = msg.reliability
    val delay = time - msg.time
    sim ! Data(time, power, reliability, delay)
  }
  
  def generateMsgs() {
    import Protocols._
    actor {
      if(me.nodeType == Source)
        for(i <- 1 to 10)
          for(d <- sim.getDestinationNodes if d.getStatus == Active) {
            me ! Message(me, d, MPMCD, time, i)
        }
      else {
        val count = 10
        for(i <- 1 to count) me ! FakeMessage
      }
      me ! FinishedMsgs
      // log ! "" + me + " generator quitting for tick " + time
    }
  }
  
  def generateFakeMsgs(count: Int) = {
    actor {
      for(i <- 1 to count) {
        me ! FakeMessage
        //Thread.`yield`
      }
      me ! FinishedMsgs
      // log ! "" + me + " fake generator quitting for tick " + time
    }
  }
  
  def send(msg: Any) = super.send(msg, null)
  
  override def start() = {
    log = sim.log
    elog = sim.elog
    
    log ! this.toString + " starting."
    running = true
    status = Active
    super.start
  }
  
  override def toString = "Node-" + id
}